using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;

namespace CodePlex.Resourceful.Amazon.S3.Actions
{
    public static class LogConcatenate
    {

        public static void Run(S3Connection cn, Tracer tracer, string bucket, string prefix, string workingDirectory, 
            string combinedFileName, bool includeOriginalCombined ,bool deleteOriginalLogs, int max)
        {

            string work = Path.Combine(workingDirectory, "LogConcatenate");

            if (Directory.Exists(work))
            {
                tracer.WriteLine("Directory '{0}' exists, deleting...", work);
                Directory.Delete(work, true);
            }
            tracer.WriteLine("Creating directory '{0}'...", work);
            Directory.CreateDirectory(work);


            List<string> localFiles = new List<string>();

            int i = 0;
            ReadOnlyList<Key> keys = cn.ListBucket(bucket, prefix);

            // short-circuit (no download)
            if (keys.Count == 0 || (keys.Count == 1 && keys[0].Name.Substring(prefix.Length) == combinedFileName))
            {
                tracer.WriteLine("..done (nothing to do).");
                return;
            }

            foreach (Key key in keys)
            {

                string filename = key.Name.Substring(prefix.Length);

                // 2007-01-02-03-15-59-79F6FA9C4977AC60

                if (
                    (Regex.IsMatch(filename, @"^\d{4}-\d{2}-\d{2}-\d{2}-\d{2}-\d{2}-\w{16}$") && (i++<max))
                    || (includeOriginalCombined && filename == combinedFileName))
                {

                    string localFile = Path.Combine(work, filename);
                    tracer.WriteLine("Downloading '{0}'...", key.Name);

                    try
                    {
                        cn.GetObjectAsFile(bucket, key.Name, localFile);
                    }
                    catch (Exception ex)
                    {
                        tracer.WriteLine("Skipping    '{0}' because of exception '{1}'...", key.Name,ex.Message);
                        continue;
                    }

                    CheckMD5(cn, bucket, key.Name, localFile);

                    localFiles.Add(localFile);
                    
                }
                else
                {
                    tracer.WriteLine("Skipping    '{0}'...", key.Name);
                }
            }



            // short-circuit (no upload)
            if (localFiles.Count==0 || (localFiles.Count ==1&& Path.GetFileName(localFiles[0]) == combinedFileName)) 
            {
                tracer.WriteLine("..done (nothing to do).");
                return;
            }


            string combinedFile = Path.Combine(work, combinedFileName+".new");
            tracer.WriteLine("Creating  file '{0}'...", combinedFile);
            using (FileStream outputStream = File.Create(combinedFile))
            {
                localFiles.Sort(delegate(string lhs, string rhs)
                {
                    if (Path.GetFileName(lhs) == combinedFileName)
                    {
                        return -1;
                    }
                    else if (Path.GetFileName(rhs) == combinedFileName)
                    {
                        return 1;
                    }
                    else
                    {
                        return lhs.CompareTo(rhs);
                    }
                });
                foreach (string localFile in localFiles)
                {
                  
                    tracer.WriteLine("Appending file '{0}'...", localFile);
                    using (FileStream inputStream = File.OpenRead(localFile))
                    {
                        Utils.BufferedTransfer(inputStream, outputStream);
                    }
                }
            }



            long expectedLength = Utils.Aggregate<long, long>(localFiles.ConvertAll<long>(delegate(string item)
            {
                return new FileInfo(item).Length;
            }),
            delegate(long item0, long item1)
            {
                return item0 + item1;
            });


            long actualLength = new FileInfo(combinedFile).Length;

            if (actualLength != expectedLength)
            {
                throw CodePlexException.Format("byte length mismatch, expected {0}, actual {1}", expectedLength, actualLength);
            }



            string md5Combined = Utils.ToBase64(Utils.ToMD5(File.ReadAllBytes(combinedFile)));

            NameValuePairs headers = new NameValuePairs();
            headers[HttpHeader.ContentMD5] = md5Combined;
            headers[HttpHeader.ContentType] = MediaType.TextPlain;

            tracer.WriteLine("Uploading file '{0}' ({1})...", combinedFile,Utils.ComputeSizeString(actualLength));
            cn.PutObjectAsStream(bucket, prefix + combinedFileName, headers, null, File.OpenRead(combinedFile), actualLength);

            CheckMD5(cn, bucket, prefix + combinedFileName, combinedFile);

            if (deleteOriginalLogs)
            {
                foreach (string localFile in localFiles)
                {
                    if (Path.GetFileName(localFile) == combinedFileName)
                    {
                        continue;
                    }
                    string key = prefix + Path.GetFileName(localFile);
                    tracer.WriteLine("Deleting original log '{0}'...", key);
                    cn.DeleteObject(bucket, key);
                }
            }


            tracer.WriteLine("...done.");
        }







        private static void CheckMD5(S3Connection cn, string bucket, string key, string localFile)
        {
            string md5Actual = Utils.ToBase16(Utils.ToMD5(File.ReadAllBytes(localFile)));
            string md5Expected = cn.GetObjectHeaders(bucket, key)[HttpHeader.ETag].Trim('"');

            if (md5Actual != md5Expected)
            {
                throw CodePlexException.Format("md5 mismatch {0}, expected {1}, actual {2}", key, md5Expected, md5Actual);
            }

        }



    }
}
